﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using Microsoft.Xna.Framework;

namespace BGSpaceShooterServer
{
    /// <summary>
    /// 
    /// </summary>
    /// <remarks>Z tej klasy sie zrobila fabryk praktyczeie... trzeba o refactoringu pomyslec</remarks>
    internal class ProjectileContainer
    {
        /// <summary>
        /// Ta sama wartosc w kliencie! Powinno sie to synchronizowac przez siec, albo lcizyc na podstawie ilosci klientow
        /// </summary>
        public const int MaxObjects = 1000;

        /// <summary>
        /// Ilosc sekund po ktorych ubijamy pocisk
        /// </summary>
        public const int OldProjectileThreshold = 10;

        /// <summary>
        /// Promień BoundingSphere'a dla asteriod. Docelowo do XML'a
        /// </summary>
        public const float asteroidBoundingSphereRadius = 25.38f;

        /// <summary>
        /// Promień BoundingSphere'a dla pocisków. Docelowo do XML'a
        /// </summary>
        public const float bulletBoundingSphereRadius = 0.845f;

        private Projectile[] Projectiles = new Projectile[MaxObjects];
        /// <summary>
        /// Wskazuej ktory element tablicy jest pierwszym pustym
        /// </summary>
        private int firstEmpty = 0;

        /// <summary>
        /// Czas ostatniego update'a
        /// </summary>
        private DateTime lastUpdate;

        /// <summary>
        /// Wypelniamy tablice domyslnymi (niekatywnymi) obiektami. I tak bedzie pelna chwile po rozpoczeciu gry, a zmniejsza ilosc ifow
        /// </summary>
        public ProjectileContainer()
        {
            for (int i = 0; i < Projectiles.Length; i++)
            {
                Projectiles[i] = new Projectile();
                Projectiles[i].IsAlive = false;
            }
            lastUpdate = DateTime.Now;
        }

        public void ClearFromOldProjectiles()
        {
            DateTime now = DateTime.Now;
            for (int i = 0; i < Projectiles.Length; i++)
            {
                if (Projectiles[i].IsAlive && Projectiles[i].ProjectileType != GameObjectName.Asteroid)
                    if ((now - Projectiles[i].CreatedTime).TotalSeconds > OldProjectileThreshold)
                        DestroyProjectile(i);
            }
        }

        private void DestroyProjectile(int id)
        {
            if (id >= MaxObjects)
                throw new Exception("Bledny ID pocisku");
            Projectiles[id].IsAlive = false;
        }

        private void FindNextEmpty()
        {
            //Zaczynamy od pierwszego pustego albo od pierwszego jesli prawdopodobnie nie ma zadnego
            for (int i = firstEmpty == -1 ? 0 : firstEmpty; i < Projectiles.Length; i++)
            {
                if (Projectiles[i].IsAlive == false)
                {
                    firstEmpty = i;
                    return;
                }
            }

            firstEmpty = -1; //Tablica jest pelna!
        }

        private Projectile CreateProjectile(DateTime creation)
        {
            lock (this)
            {
                FindNextEmpty();
                if (firstEmpty != -1)
                {
                    Projectiles[firstEmpty] = new Projectile();
                    Projectiles[firstEmpty].IsAlive = true;
                    Projectiles[firstEmpty].Id = firstEmpty;
                    Projectiles[firstEmpty].CreatedTime = creation;

                    return Projectiles[firstEmpty];
                }
            }
            return null;
        }

        public Projectile CreateAsteroid(Vector3 position)
        {
            Projectile asteroid = CreateProjectile(DateTime.Now);
            if (asteroid != null)
            {
                asteroid.HP = 1;
                asteroid.Position = position;
                asteroid.ProjectileType = GameObjectName.Asteroid;
                asteroid.BoundingSphere = new BoundingSphere(Vector3.Zero, asteroidBoundingSphereRadius);
                asteroid.PlayerId = -1;
                return asteroid;
            }
            return null;
        }

        public Projectile CreateBullet(StarFighter starFighter, int playerId, int turretIndex, DateTime creation)
        {
            Projectile projectile = CreateProjectile(creation);
            if (projectile != null)
            {
                projectile.HP = 1;
                projectile.Position = starFighter.Position +
                                      Vector3.Transform(
                                          starFighter.GetComponentByName(turretIndex == 0 ? "GunRight0" : "GunLeft0").
                                              BoundingSphere.Center, starFighter.Rotation);
                projectile.Velocity = Vector3.Transform(new Vector3(0, 0, -1), starFighter.Rotation);
                projectile.Velocity *= 1f;
                projectile.Rotation = starFighter.Rotation;
                projectile.ProjectileType = GameObjectName.Bullet;
                projectile.PlayerId = playerId;
                projectile.BoundingSphere = new BoundingSphere(Vector3.Zero, bulletBoundingSphereRadius);
                return projectile;
            }
            return null;
        }

        public void UpdateProjectiles()
        {
            TimeSpan time = DateTime.Now - lastUpdate;
            foreach (Projectile projectile in Projectiles)
            {
                projectile.UpdatePosition(time);
            }
            lastUpdate += time;
        }

        public IEnumerable<Projectile> GetAcitveProjectiles()
        {
            List<Projectile> projectiles = new List<Projectile>();

            foreach (Projectile projectile in Projectiles)
            {
                if (projectile.IsAlive)
                {
                    projectiles.Add(projectile);
                }
            }

            return projectiles;
        }

        /// <summary>
        /// Pobiera liste zombie pociskow i likwiduje ich oznaczenie jako zombie
        /// </summary>
        /// <returns>Lista zombie pociskow</returns>
        public IEnumerable<Projectile> GetZombieProjectiles()
        {
            List<Projectile> zombieProjectiles = new List<Projectile>();

            foreach (Projectile projectile in Projectiles)
            {
                if (projectile.IsZombie)
                {
                    zombieProjectiles.Add(projectile);
                    projectile.IsZombie = false;
                }
            }

            return zombieProjectiles;
        }
    }
}
